package wire

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"io"
	"massnet.org/mass/logging"
	wirepb "massnet.org/mass/wire/pb"
)

// BlockVersion is the current latest supported block version.
const BlockVersionZM = 1

const MinBlockHeaderPayloadZM = blockHeaderMinPlainSize

//type BlockHeader struct {
//	ChainID         Hash
//	Version         uint64
//	Height          uint64
//	Timestamp       time.Time
//	Previous        Hash
//	TransactionRoot Hash
//	WitnessRoot     Hash
//	ProposalRoot    Hash
//	Target          *big.Int
//	Challenge       Hash
//	PubKey          *pocec.PublicKey
//	Proof           *poc.Proof
//	Signature       *pocec.Signature
//	BanList         []*pocec.PublicKey
//}

type BlockHeaderZM struct {
	mass BlockHeader
	zcash BlockHeader
	lenMass int
}

// blockHeaderMinPlainSize is a constant that represents the number of bytes for a block
// header.
// Length = 32(ChainID) + 8(Version) + 8(Height) + 8(Timestamp) + 32(Previous) + 32(TransactionRoot)
//        + 32(WitnessRoot)+ 32(ProposalRoot) + 32(Target) + 32(Challenge) + 33(PubKey) + 16(Proof)
//        + 72(Signature) + (n*33)(BanList)
//        = 369 Bytes
const blockHeaderMinPlainSizeZM = 369

// BlockHash computes the block identifier hash for the given block header.
//func (h *BlockHeader) BlockHash() Hash {
//	buf, _ := h.Bytes(ID)
//	return DoubleHashH(buf)
//}

// Decode decodes r using the given protocol encoding into the receiver.
func (h *BlockHeaderZM) Decode(r io.Reader, mode CodecMode) (n int, err error) {
	var buf bytes.Buffer
	n64, err := buf.ReadFrom(r)
	n = int(n64)
	if err != nil {
		return n, err
	}

	switch mode {
	case DB, Packet:
		pb := new(wirepb.BlockHeaderZM)
		err = proto.Unmarshal(buf.Bytes(), pb)
		if err != nil {
			return n, err
		}
		return n, h.FromProto(pb)

	default:
		return n, ErrInvalidCodecMode
	}
}

// Encode encodes the receiver to w using the given protocol encoding.
func (h *BlockHeaderZM) Encode(w io.Writer, mode CodecMode) (n int, err error) {
	//lenMass, _ := h.mass.Encode(w, mode)
	//lenZcash, _ := h.zcash.Encode(w, mode)
	//fmt.Println(lenMass)
	//fmt.Println(lenZcash)
	//h.lenMass = lenMass

	pb := h.ToProto()

	switch mode {
	case DB, Packet:
		content, err := proto.Marshal(pb)
		fmt.Printf("content is %d", len(content))
		if err != nil {
			return 0, err
		}
		return w.Write(content)

	case Plain, ID:
		// Write every elements of blockHeader
		return pb.Write(w)

	case PoCID:
		// Write elements excepts for Signature
		return pb.WritePoC(w)

	case ChainID:
		if pb.Mass.Height > 0 {
			logging.CPrint(logging.FATAL, "ChainID only be calc for genesis block", logging.LogFormat{"height": pb.Mass.Height})
			panic(nil) // unreachable
		}
		// Write elements excepts for ChainID
		return w.Write(pb.BytesChainID())

	default:
		return 0, ErrInvalidCodecMode
	}
}

//func (h *BlockHeaderZM) Bytes(mode CodecMode) ([]byte, error) {
//	return getBytes(h, mode)
//}
//
//func (h *BlockHeaderZM) SetBytes(bs []byte, mode CodecMode) error {
//	return setFromBytes(h, bs, mode)
//}
//
//func (h *BlockHeaderZM) PlainSize() int {
//	return getPlainSize(h)
//}
//
//// Quality
//func (h *BlockHeaderZM) Quality() *big.Int {
//	pubKeyHash := pocutil.PubKeyHash(h.PubKey)
//	quality, err := h.Proof.GetVerifiedQuality(pocutil.Hash(pubKeyHash), pocutil.Hash(h.Challenge), uint64(h.Timestamp.Unix())/poc.PoCSlot, h.Height)
//	if err != nil && h.Height != 0 {
//		logging.CPrint(logging.FATAL, "fail to get header quality", logging.LogFormat{"err": err, "height": h.Height, "pubKey": hex.EncodeToString(h.PubKey.SerializeCompressed())})
//	}
//	return quality
//}
//
//// GetChainID calc chainID, only block with 0 height can be calc
//func (h *BlockHeaderZM) GetChainID() (Hash, error) {
//	if h.Height > 0 {
//		return Hash{}, errors.New(fmt.Sprintf("invalid height %d to calc chainID", h.Height))
//	}
//	buf, err := h.Bytes(ChainID)
//	if err != nil {
//		return Hash{}, err
//	}
//	return DoubleHashH(buf), nil
//}
//
//func NewBlockHeaderFromBytes(bhBytes []byte, mode CodecMode) (*BlockHeader, error) {
//	bh := NewEmptyBlockHeader()
//
//	err := bh.SetBytes(bhBytes, mode)
//	if err != nil {
//		return nil, err
//	}
//
//	return bh, nil
//}
//
//func NewEmptyBigInt() *big.Int {
//	return new(big.Int).SetUint64(0)
//}
//
//func NewEmptyPoCSignature() *pocec.Signature {
//	return &pocec.Signature{
//		R: NewEmptyBigInt(),
//		S: NewEmptyBigInt(),
//	}
//}
//
//func NewEmptyPoCPublicKey() *pocec.PublicKey {
//	return &pocec.PublicKey{
//		X: NewEmptyBigInt(),
//		Y: NewEmptyBigInt(),
//	}
//}

func NewEmptyBlockHeaderZM() *BlockHeaderZM {
	mass := NewEmptyBlockHeader()
	zcash := NewEmptyBlockHeader()
	return &BlockHeaderZM{
		mass: *mass,
		zcash: *zcash,
	}
}

//// PoCHash generate hash of all PoC needed elements in block header
//func (h *BlockHeader) PoCHash() (Hash, error) {
//	buf, err := h.Bytes(PoCID)
//	if err != nil {
//		return Hash{}, err
//	}
//
//	return DoubleHashH(buf), nil
//}

// ToProto get proto BlockHeader from wire BlockHeader
func (h *BlockHeaderZM) ToProto() *wirepb.BlockHeaderZM {
	return &wirepb.BlockHeaderZM{
		Mass: h.mass.ToProto(),
		Zcash: h.zcash.ToProto(),
	}
}

// FromProto load proto BlockHeader into wire BlockHeader
func (h *BlockHeaderZM) FromProto(pb *wirepb.BlockHeaderZM) error {
	if pb == nil {
		return errors.New("nil proto block_header")
	}
	mass, zcash := new(BlockHeader), new(BlockHeader)
	var err error
	err = mass.FromProto(pb.Mass)
	if err != nil {
		return err
	}
	err = zcash.FromProto(pb.Zcash)
	if err != nil {
		return err
	}

	h.mass = *mass
	h.zcash = *zcash

	return nil
}

//// NewBlockHeaderFromProto get wire BlockHeader from proto BlockHeader
//func NewBlockHeaderFromProto(pb *wirepb.BlockHeader) (*BlockHeader, error) {
//	h := new(BlockHeader)
//	err := h.FromProto(pb)
//	if err != nil {
//		return nil, err
//	}
//	return h, nil
//}
